package com.wep.fence.geom;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
import java.util.List;

/**
 * @author wep
 * @version 1.0
 * @description:
 * @date 2024/8/22 9:55
 */
@Data
@Slf4j
public class GeoMap {
    String map_id;
    MapStyle map_style;
    GeoPoint map_location;
    // 缩放级别
    int map_zoom_level;
    // html的头
    String html_header;
    String html_script_canvas;
    // 区域编码
    String area_code;
    // 围栏
    String area_fence;
    // 设置最小缩放级别 =0 时,进行缩放比例限制
    int min_zoom_level;
    // 最大缩放级别
    int max_zoom_level;

    /**
     * :
     *
     * @param: areaFence 区域围栏
     * @param: areaCode 区域编码
     * @param: location  地图中心经纬度
     * @param: zoomLevel 地图缩放比例
     * @param: mapType  地图样式
     * @param: minZoomLevel  最小缩放比例 设置0 则不限制
     * @param: maxZoomLevel  最大缩放比例 设置0 则不限制
     * @param: resourceType 静态资源类型:1-网络cdn;2-本地cdn
     * @return:
     * @author wep
     * @date: 2024/12/17 9:52
     */
    public GeoMap(String areaFence, String areaCode, GeoPoint location, int zoomLevel, MapStyle mapType, int minZoomLevel, int maxZoomLevel) {
        area_fence = areaFence;
        min_zoom_level = minZoomLevel;
        max_zoom_level = maxZoomLevel;

        // 获取区域编码
        area_code = areaCode;

        // Initialize map ID, location and zoom level
        if (StrUtil.isEmpty(areaCode)) {
            map_id = StrUtil.format("map_{}_{}", DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN), System.nanoTime());
        } else {
            map_id = StrUtil.format("map_{}_{}_{}", areaCode, DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN), System.nanoTime());
        }
        map_style = mapType;
        map_location = location;
        map_zoom_level = zoomLevel;

        // Prepare HTML header
        html_header = "";
        html_header += "<!DOCTYPE html>\n<head>\n\t<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n";
        html_header += "\t<script>\n\t\tL_NO_TOUCH = false;\n\t\tL_DISABLE_3D = false;\n\t</script>\n";
        html_header += "\t<style>html, body {width: 100%;height: 100%;margin: 0;padding: 0;}</style>\n";
        html_header += "\t<style>#map {position:absolute;top:0;bottom:0;right:0;left:0;}</style>\n";
        html_header += "\t<link rel=\"stylesheet\" href=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.css\" />\n";

        String[] script_src_list = new String[]{
                "../assets/map/leaflet.js",
                "../assets/map/jquery-1.12.4.min.js",
                "../assets/map/bootstrap.min.js",
                "../assets/map/leaflet.awesome-markers.js"
        };
        for (String script_src : script_src_list) {
            html_header += String.format("\t<script src=\"%s\"></script>\n", script_src);
        }
        String[] href_list = new String[]{
                "../assets/map/leaflet.css",
                "../assets/map/bootstrap.min.css",
                "../assets/map/bootstrap-theme.min.css",
                "../assets/map/font-awesome.min.css",
                "../assets/map/leaflet.awesome-markers.css",
                "../assets/map/leaflet.awesome.rotate.min.css"
        };

        for (String href : href_list) {
            html_header += String.format("\t<link rel=\"stylesheet\" href=\"%s\"/>\n", href);
        }

        html_header += "\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />\n";

        html_header += String.format("""
                \t<style>
                \t\t#%s {
                \t\t\tposition: relative;
                \t\t\twidth: 100.0%%;
                \t\t\theight: 100.0%%;
                \t\t\tleft: 0.0%%;
                \t\t\ttop: 0.0%%;
                \t\t}
                
                \t\t.area-tooltip {
                \t\t\tbackground-color: #f0f8ff;
                \t\t\tcolor: #333;
                \t\t\tpadding: 6px 10px;
                \t\t\tborder-radius: 8px;
                \t\t\tborder: 1px solid #9ACD32;
                \t\t\tfont-size: 10px;
                \t\t\tfont-weight: bold;
                \t\t\tbox-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
                \t\t}
                </style>
                </head>
                """, map_id);

        html_header += String.format("<body>\n\t<div class=\"folium-map\" id=\"%s\" ></div>\n</body>\n", map_id);

        html_script_canvas = "";
    }


    public static String getDrawPolyline(List<GeoPoint> polyline, String color, double opacity, int weight) {
        StringBuilder locations = new StringBuilder();
        for (GeoPoint loc : polyline) {
            locations.append(String.format("[%f,%f],", loc.getLatitude(), loc.getLongitude()));
        }
        StringBuilder buf = new StringBuilder(locations.toString());
        buf.deleteCharAt(buf.length() - 1);

        return String.format("\n\tL.polyline([%s],{\"bubblingMouseEvents\": true, \"color\": \"%s\", \"dashArray\": null, \"dashOffset\": null, \"fill\": false, \"fillColor\": \"%s\", \"fillOpacity\": %f, \"fillRule\": \"evenodd\", \"lineCap\": \"round\", \"lineJoin\": \"round\", \"noClip\": false, \"opacity\": %f, \"smoothFactor\": 1.0, \"stroke\": true, \"weight\": %d})\n",
                buf, color, color, opacity, opacity, weight);
    }

    public static String getDrawCircle(GeoPoint location, double radius, int weight, String color, double opacity, boolean fill, String fillColor, double fillOpacity) {
        return String.format("\n\tL.circle([%f, %f],{\"bubblingMouseEvents\": true, \"color\": \"%s\", \"dashArray\": null, \"dashOffset\": null, \"fill\": %s, \"fillColor\": \"%s\", \"fillOpacity\": %f, \"fillRule\": \"evenodd\", \"lineCap\": \"round\", \"lineJoin\": \"round\", \"opacity\": %f, \"radius\": %f, \"stroke\": true, \"weight\": %d})\n",
                location.getLatitude(), location.getLongitude(), color, fill, fillColor, fillOpacity, opacity, radius, weight);
    }


    /**
     * Set the location of map
     * 设置地图的位置
     *
     * @param location
     */
    public void setLocation(GeoPoint location) {
        this.map_location = location;
    }

    /**
     * Set the zoom level of map
     * 设置地图的缩放级别
     *
     * @param zoomLevel
     */
    public void setZoomLevel(int zoomLevel) {
        this.map_zoom_level = zoomLevel;
    }

    /**
     * Reset the canvas
     * 重置画布
     */
    public void reset() {
        html_script_canvas = "";
    }

    /**
     * save the map to html file
     * 将地图保存到HTML文件
     *
     * @param prefix 前缀
     * @param suffix 后缀
     */
    /**
     * save the map to html file
     * 将地图保存到HTML文件
     */
    public File save(String filename) {
        File file = FileUtil.touch(filename);
        try (FileWriter fileWriter = new FileWriter(file);) {
            fileWriter.write(getHtmlContent());
            fileWriter.flush();
            return file;
        } catch (IOException e) {
            log.error("绘制的地图失败,原因:{}", e.getMessage());
            return null;
        }
    }

    public File file(String prefix, String suffix) {
        return this.save(prefix + suffix);
    }

    public byte[] visualize() {
        String htmlContent = getHtmlContent();
        return htmlContent.getBytes();
    }

    public String getHtmlContent() {
        String script_header_1 = String.format("var %s=L.map(\"%s\",{center:[%f,%f],crs: L.CRS.EPSG3857,zoom: %d,zoomControl: true,preferCanvas: false});",
                map_id, map_id, map_location.latitude, map_location.longitude, map_zoom_level);

        String title_layer_url;
        String title_layer_attribute = switch (map_style) {
            case Satellite -> {
                title_layer_url = "https://webst02.is.autonavi.com/appmaptile?style=6\u0026x={x}\u0026y={y}\u0026z={z}";
                yield "\u9ad8\u5fb7-\u536b\u661f\u5f71\u50cf\u56fe";
            }
            case ChineseEnglish -> {
                title_layer_url = "https://webrd02.is.autonavi.com/appmaptile?lang=zh_en\u0026size=1\u0026scale=1\u0026style=8\u0026x={x}\u0026y={y}\u0026z={z}";
                yield "\u9ad8\u5fb7-\u4e2d\u82f1\u6587\u5bf9\u7167";
            }
            case Grayscale -> {
                title_layer_url = "https://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png";
                yield "Map tiles by \u003ca href=\\\"http://stamen.com\\\"\u003eStamen Design\u003c/a\u003e, under \u003ca href=\\\"http://creativecommons.org/licenses/by/3.0\\\"\u003eCC BY 3.0\u003c/a\u003e. Data by \u0026copy; \u003ca href=\\\"http://openstreetmap.org\\\"\u003eOpenStreetMap\u003c/a\u003e, under \u003ca href=\\\"http://www.openstreetmap.org/copyright\\\"\u003eODbL\u003c/a\u003e.";
            }
            case Darkblue -> {
                title_layer_url = "http://t7.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=8874912ba1eb8e0f644372025baa1980";
                yield "\u84dd\u9ed1\u7248";
            }
            default -> {
                title_layer_url = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
                yield "Data by \u0026copy; \u003ca href=\\\"http://openstreetmap.org\\\"\u003eOpenStreetMap\u003c/a\u003e, under \u003ca href=\\\"http://www.openstreetmap.org/copyright\\\"\u003eODbL\u003c/a\u003e.";
            }
        };

        // 设置最小缩放比例
        String setMin_zoom_level = "";
        if (min_zoom_level != 0) {
            setMin_zoom_level = StrUtil.format("{}.setMinZoom({})", map_id, min_zoom_level);
        }
        String setMax_zoom_level = "";
        if (max_zoom_level != 0) {
            setMax_zoom_level = StrUtil.format("{}.setMaxZoom({})", map_id, max_zoom_level);
        }
        if (StrUtil.isNotEmpty(setMin_zoom_level)) {
            script_header_1 += ("\n" + setMin_zoom_level);
        }
        if (StrUtil.isNotEmpty(setMax_zoom_level)) {
            script_header_1 += ("\n" + setMax_zoom_level);
        }

        String script_header_2 = String.format("var title_layer_%d = L.tileLayer(\"%s\",{\"attribution\": \"%s\", \"detectRetina\": false, \"maxNativeZoom\": 18, \"maxZoom\": 18, \"minZoom\": 0, \"noWrap\": false, \"opacity\": 1, \"subdomains\": \"abc\", \"tms\": false}).addTo(%s);",
                System.nanoTime(), title_layer_url, title_layer_attribute, map_id);

        // 添加地名图层
        String toponym_layer_url = "https://t7.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TileCol={x}&TileRow={y}&TileMatrix={z}&tk=8874912ba1eb8e0f644372025baa1980";
        String script_header_3 = String.format("var toponym_%d = L.tileLayer(\"%s\",{\"attribution\": \"%s\", \"detectRetina\": false, \"maxNativeZoom\": 18, \"maxZoom\": 18, \"minZoom\": 0, \"noWrap\": false, \"opacity\": 1, \"subdomains\": \"abc\", \"tms\": false}).addTo(%s);",
                System.nanoTime(), toponym_layer_url, title_layer_attribute, map_id);

        String html_script;
        if (StrUtil.isNotEmpty(area_fence)) {
            String geoJSON1 = StrUtil.format("var geoJSONs = {}", area_fence);
            String geoJSON2 = StrUtil.format("var geoJSONLayer = L.geoJSON(geoJSONs, {\n" +
                    "\t\tstyle: function (feature) {\n" +
                    "\t\t\treturn {color: 'blue', weight: 2};\n" +
                    "\t\t}\n" +
                    "\t}).addTo({});", map_id);
            // 设置自适应地图缩放比例
            String geoJSON3 = StrUtil.format("geoJSONLayer.addTo({})\n" +
                    "var bounds = geoJSONLayer.getBounds();\n" +
                    "{}.fitBounds(bounds)", map_id, map_id);
            html_script = String.format("\n<script>\n\t%s\n\n\t%s\n\n\t%s\n\n\t%s\n\n\t%s\n\n\t%s\n\t%s</script>", script_header_1,
                    script_header_2, script_header_3, geoJSON1, geoJSON2, geoJSON3, html_script_canvas);
        } else {
            html_script = String.format("\n<script>\n\t%s\n\n\t%s\n\n\t%s\n\t%s</script>", script_header_1,
                    script_header_2, script_header_3, html_script_canvas);
        }

        return html_header + html_script;
    }

    /**
     * Draw polyline on map
     * <p>
     * 在地图上绘制折线
     *
     * @param polyline
     * @param color
     * @param opacity
     * @param weight
     */
    public void drawPolyline(List<GeoPoint> polyline, String color, double opacity, int weight) {
        if (CollUtil.isEmpty(polyline)) {
            return;
        }
        StringBuilder locations = new StringBuilder();

        for (GeoPoint loc : polyline) {
            locations.append(String.format("[%f,%f],", loc.latitude, loc.longitude));
        }
        StringBuilder buf = new StringBuilder(locations.toString());
        buf.deleteCharAt(buf.length() - 1);


        String name = StrUtil.format("poly_line_{}", System.nanoTime());
        String canvas = String.format("\n\tvar %s = L.polyline([%s],{\"bubblingMouseEvents\": true, \"color\": \"%s\", \"dashArray\": null, \"dashOffset\": null, \"fill\": false, \"fillColor\": \"%s\", \"fillOpacity\": %f, \"fillRule\": \"evenodd\", \"lineCap\": \"round\", \"lineJoin\": \"round\", \"noClip\": false, \"opacity\": %f, \"smoothFactor\": 1.0, \"stroke\": true, \"weight\": %d}).addTo(%s);\n",
                name, buf, color, color, opacity, opacity, weight, map_id);

        html_script_canvas += canvas;
    }

    /**
     * Draw hollow circle on map
     * <p>
     * 在地图上画一个空心圆
     *
     * @param location
     * @param radius
     * @param weight
     * @param color
     * @param opacity
     */
    public void drawCircle(GeoPoint location, int radius, int weight, String color, double opacity, String popup) {
        this.drawCircle(location, radius, weight, color, opacity, false, color, opacity, popup);
    }

    /**
     * Draw solid circle on map
     * <p>
     * 在地图上画实圆
     *
     * @param location
     * @param radius      半径
     * @param weight
     * @param color
     * @param opacity
     * @param fill
     * @param fillColor
     * @param fillOpacity
     */
    public void drawCircle(GeoPoint location, double radius, int weight, String color, double opacity, boolean fill, String fillColor, double fillOpacity, String popup) {
        long ts = System.nanoTime();
        String circle_id = String.format("circle_%d", ts);

        String canvas = String.format("\n\tvar %s = L.circle([%f, %f],{\"bubblingMouseEvents\": true, \"color\": \"%s\", \"dashArray\": null, \"dashOffset\": null, \"fill\": %s, \"fillColor\": \"%s\", \"fillOpacity\": %f, \"fillRule\": \"evenodd\", \"lineCap\": \"round\", \"lineJoin\": \"round\", \"opacity\": %f, \"radius\": %f, \"stroke\": true, \"weight\": %d}).addTo(%s);\n",
                circle_id, location.latitude, location.longitude, color, fill, fillColor, fillOpacity, opacity, radius, weight, map_id);

        if (popup != null) {
            String popup_id = String.format("popup_%d", ts);
            String html_id = String.format("html_%d", ts);

            canvas += String.format("\tvar %s = L.popup({\"maxWidth\": \"100%%\"});\n", popup_id);
            canvas += String.format("\tvar %s = $(`<div id=\"%s\" style=\"width: 100.0%%; height: 100.0%%;\">%s</div>`)[0];\n", html_id, html_id, popup);
            canvas += String.format("\t%s.setContent(%s);\n", popup_id, html_id);
            canvas += String.format("\t%s.bindPopup(%s);\n", circle_id, popup_id);
        }
        html_script_canvas += canvas;
    }

    /**
     * Draw marker on map
     * 在地图上画标记
     *
     * @param location
     * @param popup
     */
    public void drawMarker(GeoPoint location, String popup) {
        this.drawMarker(location, popup, null, null, null);
    }

    /**
     * Draw marker on map with icon style
     * 用图标样式在地图上绘制标记
     * https://fontawesome.com/icons?d=gallery
     *
     * @param location
     * @param popup
     * @param iconName
     */
    public void drawMarker(GeoPoint location, String popup, String iconName) {
        this.drawMarker(location, popup, iconName, null, null);
    }

    /**
     * Draw marker on map with colored icon
     * 用彩色图标在地图上画标记
     * https://fontawesome.com/icons?d=gallery
     *
     * @param location
     * @param popup
     * @param iconName    # car, bus, plane, home, bed, bank, heart, circle, square, wifi, phone, paw, tree, tv, key
     * @param iconColor
     * @param markerColor
     */
    public void drawMarker(GeoPoint location, String popup, String iconName, String iconColor, String markerColor) {
        long ts = System.nanoTime();
        String marker_id = String.format("marker_%d", ts);

        String canvas = "\n";

        canvas += String.format("\tvar %s = L.marker([%f, %f],{}).addTo(%s);\n", marker_id, location.latitude, location.longitude, map_id);

        if (iconName != null) {
            String icon_id = String.format("icon_%d", ts);
            iconColor = iconColor == null ? "white" : iconColor;
            markerColor = markerColor == null ? "blue" : markerColor;
            canvas += String.format("\tvar %s = L.AwesomeMarkers.icon({\"extraClasses\": \"fa-rotate-0\", \"icon\": \"%s\", \"iconColor\": \"%s\", \"markerColor\": \"%s\", \"prefix\": \"fa\"});\n", icon_id, iconName, iconColor, markerColor);
            canvas += String.format("\t%s.setIcon(%s);\n", marker_id, icon_id);
        }

        if (popup != null) {
            String popup_id = String.format("popup_%d", ts);
            String html_id = String.format("html_%d", ts);
            canvas += String.format("\tvar %s = L.popup({\"maxWidth\": \"100%%\"});\n", popup_id);
            canvas += String.format("\tvar %s = $(`<div id=\"%s\" style=\"width: 100.0%%; height: 100.0%%;\">%s</div>`)[0];\n", html_id, html_id, popup);
            canvas += String.format("\t%s.setContent(%s);\n", popup_id, html_id);
            canvas += String.format("\t%s.bindPopup(%s);\n", marker_id, popup_id);
        }

        html_script_canvas += canvas;
    }


    public enum MapStyle {
        Default,
        Satellite,
        ChineseEnglish,
        Grayscale,
        Darkblue,
    }
}
